问题描述
删除RDS PostgreSQL账号时,报错:
在RDS PostgreSQL控制台删除账号时,出现错误提示。其中:
报错信息:数据库对象依赖该操作的账号,请先解除依赖后,再操作。
错误码:
AccountActionForbidden
。
使用SQL命令删除在账号时报错:
ERROR: role "<username>" cannot be dropped because some objects depend on it
问题原因
在RDS PostgreSQL中,如果存在数据库对象依赖于即将被删除的账号,将导致删除操作失败。
解决方案
批量处理(粗粒度、简洁高效):使用SQL命令删除目标账号,根据报错中提示,先将待删除账号拥有的对象批量转移给其他用户,然后再撤销其所有权限。
精细处理(细粒度、每个权限和对象透明可控):查找依赖目标账号的对象,并将其逐一删除。
批量处理
分析报错信息,确认目标账号的拥有的对象及权限。以问题重现示例为例:
DROP USER user_to_be_dropped; ERROR: role "user_to_be_dropped" cannot be dropped because some objects depend on it DETAIL: privileges for database testdb01 owner of database testdb02 privileges for membership of role testdbuser in role user_to_be_dropped_2 4 objects in database testdb01 2 objects in database testdb02
从报错信息中可以确认,目标账号
user_to_be_dropped
在testdb01和testdb02中存在依赖对象。其中:在数据库testdb01中,目标账号具备对某些对象(共4个对象)的权限(privileges)。
目标账号是数据库testdb02的拥有者(owner)。
使用高权限账号(本文以testdbuser为例),分别登录报错信息中涉及的数据库,将目标账号拥有的对象批量转移给其他用户(本文以高权限账号testdbuser为例),然后删除目标账号拥有的所有对象的权限。
登录数据库testdb01,删除目标账号
user_to_be_dropped
拥有的对象。DROP OWNED BY user_to_be_dropped;
登录数据库testdb02,将所有权转移给其他账号,并删除目标账号
user_to_be_dropped
拥有的对象。--转移所有权 REASSIGN OWNED BY user_to_be_dropped TO testdbuser; --删除目标账号拥有的对象 DROP OWNED BY user_to_be_dropped;
精细处理
步骤一:查找依赖对象
系统表pg_shdepend
中记录了单个数据库内部的对象对全局对象的依赖,可以用于查询哪些对象依赖了目标账号。例如,查询哪些对象依赖账号 user_to_be_dropped
。
使用高权限账号登录数据库实例,查询与账号
user_to_be_dropped
相关联的对象。WITH role as (SELECT oid FROM pg_roles WHERE rolname = 'user_to_be_dropped') SELECT db.datname AS database, pg_class.relname AS classname, shp.objid AS oid, CASE WHEN shp.deptype = 'o' THEN 'Object Owner' WHEN shp.deptype = 'a' THEN 'In Access Control List' WHEN shp.deptype = 'r' THEN 'Policy Object' ELSE 'CANNOT HAPPEN' END FROM pg_shdepend shp LEFT JOIN pg_database db ON shp.dbid = db.oid JOIN pg_class ON shp.classid = pg_class.oid WHERE shp.refclassid = 1260 AND shp.refobjid IN (SELECT oid FROM role);
查询结果为:
database | classname | oid | case ----------+-----------------+-------+------------------------ testdb01 | pg_namespace | 2200 | In Access Control List | pg_database | 16399 | In Access Control List testdb01 | pg_namespace | 16402 | Object Owner testdb01 | pg_class | 16403 | Object Owner testdb01 | pg_class | 16406 | Object Owner | pg_database | 16409 | Object Owner testdb02 | pg_namespace | 16410 | Object Owner testdb02 | pg_class | 16411 | Object Owner | pg_auth_members | 16416 | In Access Control List
其中各个字段的含义如下。
字段
含义
database
指明依赖对象所在的库,为空时表示全局对象的依赖。
classname
指明系统表名称。
oid
指明依赖对象的oid。
case
指明依赖类型,常见的类型包括:
Owner:被删除账号是该对象的所有者。
ACL(Access Control List):被删除账号在Access Control List中。
使用高权限账号登录到对应数据库中,在对应的系统表中查询上一步获得的
oid
所对应的对象名称,例如。全局对象的依赖
database
字段为空,表示全局对象的依赖。oid=16399
,testdb01
的ACL中有user_to_be_dropped
的相关条目。SELECT datname, datdba::regrole, datacl FROM pg_database WHERE oid = 16399; datname | datdba | datacl ----------+------------+------------------------------------------------------------------------------ testdb01 | testdbuser | {=Tc/testdbuser,testdbuser=CTc/testdbuser,user_to_be_dropped=CTc/testdbuser} (1 row)
oid=16409
,testdb02
的所有者为user_to_be_dropped
。SELECT datname, datdba::regrole, datacl FROM pg_database WHERE oid = 16409; datname | datdba | datacl ----------+--------------------+-------- testdb02 | user_to_be_dropped | (1 row)
oid=16416
,pg_auth_members
中的角色从属关系,记录了授予user_to_be_dropped
账号的相应权限。SELECT oid, roleid::regrole, member::regrole, grantor::regrole FROM pg_auth_members WHERE oid = 16416; oid | roleid | member | grantor -------+----------------------+------------+-------------------- 16416 | user_to_be_dropped_2 | testdbuser | user_to_be_dropped (1 row)
单库内对象的依赖
database
字段不为空,表示该库下存在依赖对象。oid=2200
,testdb01
下public schema
的ACL中有user_to_be_dropped
的相关条目。SELECT nspname, nspowner::regrole, nspacl FROM pg_namespace WHERE oid = 2200; nspname | nspowner | nspacl ---------+------------+--------------------------------------------------------------------------- public | testdbuser | {testdbuser=UC/testdbuser,=U/testdbuser,user_to_be_dropped=UC/testdbuser} (1 row)
oid=16403
,testdb01
下的表test_nsp.test_tbl
的所有者为user_to_be_dropped
。SELECT relname, relnamespace::regnamespace, relowner::regrole, relacl FROM pg_class WHERE oid = 16403; relname | relnamespace | relowner | relacl ----------+--------------+--------------------+-------- test_tbl | test_nsp | user_to_be_dropped | (1 row)
步骤二:处理依赖对象
您可以根据实际需求:
如果是ACL类型的依赖,可以收回对应的权限。
如果是Owner类型的依赖,可以将其所有权转移给其他账号,也可以删除对应的依赖。
例如:
ACL类型的依赖
oid=2200
,收回testdb01
下public schema
的权限。SELECT nspname, nspowner::regrole, nspacl FROM pg_namespace WHERE oid = 2200; nspname | nspowner | nspacl ---------+------------+--------------------------------------------------------------------------- public | testdbuser | {testdbuser=UC/testdbuser,=U/testdbuser,user_to_be_dropped=UC/testdbuser} (1 row) REVOKE ALL ON SCHEMA public FROM user_to_be_dropped;
oid=16399
,收回testdb01
的权限。SELECT datname, datdba::regrole, datacl FROM pg_database WHERE oid = 16399; datname | datdba | datacl ----------+------------+------------------------------------------------------------------------------ testdb01 | testdbuser | {=Tc/testdbuser,testdbuser=CTc/testdbuser,user_to_be_dropped=CTc/testdbuser} (1 row) REVOKE ALL ON DATABASE testdb01 from user_to_be_dropped;
oid=16416
,对于包含角色从属关系的情况,RDS PostgreSQL 16 及以上版本中,需要切换到user_to_be_dropped
账号,删除user_to_be_dropped_2
的高权限账号(本文以testdbuser为例)的权限。\c testdb01 user_to_be_dropped; You are now connected to database "testdb01" as user "user_to_be_dropped". REVOKE user_to_be_dropped_2 FROM testdbuser cascade;
Owner类型依赖所有权转移给其他账号。
使用高权限账单登录到对应数据库。
将对象的所有权转移给其他账号。
oid=16403
,将testdb01
下的表test_nsp.test_tbl
的所有者权限转移给其他账号(本文以testdbuser为例)。SELECT relname, relnamespace::regnamespace, relowner::regrole, relacl FROM pg_class WHERE oid = 16403; relname | relnamespace | relowner | relacl ----------+--------------+--------------------+-------- test_tbl | test_nsp | user_to_be_dropped | (1 row) ALTER TABLE test_nsp.test_tbl OWNER TO testdbuser;
删除依赖对象。
删除测试数据库testdb02。
DROP DATABASE testdb02;
删除
oid=16402
的对象。SELECT nspname, nspowner::regrole, nspacl FROM pg_namespace WHERE oid = 16402; nspname | nspowner | nspacl ----------+--------------------+-------- test_nsp | user_to_be_dropped | (1 row) DROP SCHEMA test_nsp cascade;
删除
oid=16406
的对象。SELECT relname, relnamespace::regnamespace, relowner::regrole, relacl FROM pg_class WHERE oid = 16406; relname | relnamespace | relowner | relacl ----------+--------------+--------------------+-------- test_tbl | public | user_to_be_dropped | (1 row) DROP TABLE public.test_tbl;
返回结果:
database | classname | oid | case
----------+-----------+-----+------
(0 rows)
步骤三:删除目标账号
在RDS PostgreSQL控制台或通过SQL命令删除目标账号。SQL示例如下。
DROP USER user_to_be_dropped;
删除目标账号
在RDS PostgreSQL控制台或通过SQL命令删除目标账号。SQL示例如下。
DROP USER user_to_be_dropped;